import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;


public class Method {
	
	private static final String SERVER_URL = "http://ws.audioscrobbler.com/2.0/";
	
	private Method() {}
	
	public static Document call(Map<String, String> parameterMap, String secret) throws Exception {
		
		// add method signature to parameters
		parameterMap.put("api_sig", createApiSignature(parameterMap, secret));
        
		URL url = new URL(SERVER_URL);
        
		HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();		
		httpConnection.addRequestProperty("User-Agent", "Profile/MIDP-2.0 Configuration/CLDC-1.1");
		httpConnection.setRequestMethod("POST");
		httpConnection.setDoOutput(true);		
		
        OutputStreamWriter wr = new OutputStreamWriter(httpConnection.getOutputStream());
        wr.write(createQuery(parameterMap, "UTF-8"));
        wr.flush();
        wr.close();
		
		InputStream is = null;
		try {
			// HTTP 200 OK
			is = httpConnection.getInputStream();
		} catch (IOException e) {
			// HTTP 400 BAD REQUEST / 403 FORBIDDEN
			is = httpConnection.getErrorStream();
		}
		
		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
        Document document = docBuilder.parse (is);
        
        // close stream and disconnect
        is.close();
		try {			
			httpConnection.disconnect();
		} catch (Exception e) {}
        
		Node status = document.getDocumentElement().getAttributeNode("status");
        if (!"ok".equals(status.getNodeValue())) {
        	String errorMessage = document.getElementsByTagName("error").item(0).getTextContent();
        	throw new Exception(errorMessage);
        }
		
        return document;
	}
	
	private static String createApiSignature(Map<String,String> parameters, String secret) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		
		StringBuilder rawSignature = new StringBuilder();
		
		List<String> keys = new ArrayList<String>();		
		for (Iterator<String> iter = parameters.keySet().iterator(); iter.hasNext();) {
			keys.add(iter.next());
		}
		Collections.sort(keys);
		
		for (Iterator<String> iter = keys.iterator(); iter.hasNext();) {
			String key = iter.next();
			rawSignature.append(key);
			rawSignature.append(parameters.get(key));
		}
		rawSignature.append(secret);
		
		return CryptoTool.md5(rawSignature.toString());
	}
	
	private static String createQuery(Map<String, String> parameterMap, String charset) {
	    StringBuilder query = new StringBuilder();
	    
	    boolean b = true;
	    for (Iterator<String> names = parameterMap.keySet().iterator(); names.hasNext();) {
	        String name = names.next();
	        String value = parameterMap.get(name);	
	        
	        try {
	        	if (b) {
	        		b = false;
	        	} else 
	        		query.append("&");
	        	
				query.append(URLEncoder.encode(name, charset));
				query.append("=");
		        query.append(URLEncoder.encode(value, charset));
		        
			} catch (UnsupportedEncodingException e) {
			}
	    }
	
	    return query.toString();
	}
}
